
Ext.define('FrontSuite.view.Grid', {
    extend:       'Ext.grid.Panel',  // defines the superclass. data in class inheritance.
    alias:        'widget.fsgrid',   // defines an xtype 'fsgrid'

    id:              null,
    tabId:           null,
    isAggregate:     false,

    requires: [
        'Ext.grid.plugin.RowEditing',
        // 'Ext.grid.plugin.CellEditing',
        'Ext.form.field.Text',
        'Ext.toolbar.TextItem',
        'Ext.ux.grid.FiltersFeature'
    ],

    // We do the minimum in the constructor that we can.
    // This amounts to doing something with non-standard constructor arguments.
    // We do the rest in initComponent().
    constructor: function (config) {
        var tabId   = config.tabId;
        var viewDef = config.viewDef;
        var dbPart  = tabId.split('.');
//console.log('Grid.constructor');
//console.log(config);

        this.id            = 'grid-panel-' + tabId;
        this.tabId         = tabId;
        this.databaseName  = dbPart[0];
        this.tableName     = dbPart[1];
        this.viewName      = (dbPart.length > 2) ? dbPart[2] : null;
        this.tableLabel    = viewDef.table_label;

        this.itemsPerPage  = viewDef.itemsPerPage ? viewDef.itemsPerPage : 100;
        this.isAggregateOK = (!viewDef.sql && this.viewName) ? true : false;    // a literal SQL view or a native view cannot be aggregated
        this.isAggregate   = (this.isAggregateOK && !viewDef.sql && viewDef.aggregate) ? true : false;
        this.viewDef       = viewDef;

        this.relationship   = {};
        this.relationships  = [];
        FrontSuite.db.getTableDefRels(viewDef, this.relationship, this.relationships);

        this.superclass.constructor.call(this, this.id);
    },

    // Within initComponent(), we do the most possible within Ext.apply() and a minimum in code outside of it.
    // We prefer setting any object attributes via Ext.apply() rather than "this.attribute = value".
    // The main reason we use this.createXXX() methods rather than just specifying a config in Ext.apply() is that
    // there are extensive handlers on some of these widgets, and inclusion of that code in the config would decrease
    // ease-of-understanding of the code.
    // However, this is not a mandatory general pattern. Any widget which is simple enough to be described
    // simply with a config in Ext.apply() should be done that way rather than with a this.createXXX() method.
    // The key is which method makes the code easier to read and understand in its entirety.
    initComponent: function() {
        this.gridExtras                = { id: this.tabId };
        var gridColumns           = FrontSuite.db.getTableDefCols(this.viewDef, this.gridExtras);
        var gridStore             = this.createTableDataStore();

        var saveButton            = this.createSaveButton();
        var saveAsButton          = this.createSaveAsButton();
        var taskButton            = this.createTaskButton();
        var sqlButton             = this.createSqlButton();
        var operationsMenu        = this.createOperationsMenu();
        var operationsMenuButton  = this.createOperationsMenuButton(operationsMenu);
        var relationshipsComboBox = this.createRelationshipsComboBox();
        var aggregatebutton       = this.createAggregateButton();
        this.customLabelMenu      = this.createCustomLabelMenu();
        this.formatComboMenu      = this.createFormatComboMenu(this.gridExtras);
        var exportMenu            = this.createExportMenu();
        var exportMenuButton      = this.createExportMenuButton(exportMenu);
        // var selTypeButton         = this.createSelTypeButton();
        // var editButton            = this.createEditButton();

        // this.editMethod = Ext.create('Ext.grid.plugin.CellEditing', { clicksToEdit: 2 })
        this.editMethod = Ext.create('Ext.grid.plugin.RowEditing', { clicksToEdit: 2, clicksToMoveEditor: 2 });

        var dockedGridPanelItems = [
            saveButton,
            saveAsButton,
            taskButton,
            sqlButton,
            aggregatebutton,
            relationshipsComboBox,
            // exportMenuButton,
            operationsMenuButton,
            '->',
            // NOTE: The following tools are to support the in-table editing capability
            // selTypeButton,
            // editButton,
            {
                iconCls: 'icon-add',
                text: 'Add',
                scope: this,
                handler: this.onAddClick
            }, {
                iconCls: 'icon-delete',
                text: 'Delete',
                disabled: true,
                itemId: 'delete',
                scope: this,
                handler: this.onDeleteClick
            }
            // NOTE: The following tools are temporary for experimentation during development.
            //       I don't yet completely understand them or their impact.
            //       They come from the following example:
            // http://docs.sencha.com/extjs/4.1.1/#!/example/writer/writer.html
            // http://docs.sencha.com/extjs/4.1.1/extjs-build/examples/writer/writer.js
            //{
            //    text: 'AutoSync',
            //    enableToggle: true,
            //    pressed: true,
            //    tooltip: 'When enabled, Store will execute Ajax requests as soon as a Record becomes dirty.',
            //    scope: this,
            //    toggleHandler: function(btn, pressed) { this.store.autoSync = pressed; }
            //}, {
            //    text: 'Batch',
            //    enableToggle: true,
            //    pressed: true,
            //    tooltip: 'When enabled, Store will batch all records for each type of CRUD verb into a single Ajax request.',
            //    scope: this,
            //    toggleHandler: function(btn, pressed) { this.store.getProxy().batchActions = pressed; }
            //}, {
            //    text: 'WriteAllFields',
            //    enableToggle: true,
            //    pressed: false,
            //    tooltip: 'When enabled, Writer will write *all* fields to the server -- not just those that changed.',
            //    scope: this,
            //    toggleHandler: function(btn, pressed) { this.store.getProxy().getWriter().writeAllFields = pressed; }
            //}, {
            //    text: 'Sync',
            //    iconCls: 'icon-save',
            //    scope: this,
            //    handler: this.onSync
            //}
        ];

        Ext.apply(this, {
            autoScroll  : true,
            // iconCls: 'icon-user',
            // title:   'Users',
            // frame:   true,
            stripeRows  : true,
            columnLines : true,
            selType     : 'rowmodel',
            // selType     : 'cellmodel',
            selModel    : {
                //mode: 'MULTI',
                //allowDeselect: true,
                preventWrap: true
            },
            store       : gridStore,
            columns     : gridColumns,
            plugins     : [ this.editMethod ],
            features: [{
                ftype  : 'filters',
                encode : true
            }],
            viewConfig : {
                enableTextSelection: true
            },
            emptyText: 'No matching rows were found. Try adding data, reducing the filters that are set, or going back to page 1.',
            dockedItems: [{
                xtype: 'toolbar',
                id: 'toolbar-grid-panel-' + this.tabId,
                items: dockedGridPanelItems
            }, {
                itemId: 'pager',
                xtype: 'pagingtoolbar',
                dock: 'bottom',
                displayInfo: true,
                items: [
                    '-', {
                    itemId       : 'pagesize',
                    xtype        : 'combobox',
                    fieldLabel   : 'Page Size',
                    labelAlign   : 'right',
                    labelWidth   : 75,
                    labelPad     : 3,
                    width        : 140,
                    value        : 100,
                    displayField : 'id',
                    valueField   : 'id',
                    store : new Ext.data.ArrayStore({
                        fields : ['id'],
                        data   : [['5'],['15'],['25'],['50'],['100'],['150'],['300'],['500'],['1000']]
                    }),
                    listeners : {
                        select : function (field, records, eOpts) { field.up('grid').refreshPageSize(); },
                        blur   : function (field, records, eOpts) { field.up('grid').refreshPageSize(); },
                        specialkey: function(field, e) {
                            // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN, e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
                            if (e.getKey() == e.ENTER) { field.up('grid').refreshPageSize(); }
                        }
                    }
                }]
            }]
        });

        this.callParent();
        this.getSelectionModel().on('selectionchange', this.onSelectChange, this);
        this.down('#pager').bindStore(this.store, true);
    },

    refreshPageSize: function () {
        var grid = this;
        var pageSizeCombo = grid.down('#pagesize');
        var rawPageSize = pageSizeCombo.getRawValue();
        var pageSize = parseInt(rawPageSize, 10);
        if (isNaN(pageSize) || pageSize <= 0) pageSize = 100;
        if (pageSize != rawPageSize) {
            pageSizeCombo.setValue(pageSize);
            //FrontSuite.main.message('Notify','Fixed value from '+rawPageSize+' to '+pageSize);
        }
        grid.store.pageSize = pageSize;
        grid.down('pagingtoolbar').doRefresh();
    },
    
    onSelectChange: function(selModel, selections) {
        this.down('#delete').setDisabled(selections.length == 0);
    },

    onSync: function() {
        this.store.sync();
    },

    onDeleteClick: function() {
        var selection = this.getView().getSelectionModel().getSelection()[0];
        if (selection) {
            this.store.remove(selection);
        }
    },

    onAddClick: function() {
        var rec = this.newModelInstance();
        var editMethod = this.editMethod;
        editMethod.cancelEdit();
        this.store.insert(0, rec);
        //editMethod.startEditByPosition({
        //    row: 0,
        //    column: 1
        //});
    },

    listeners: {
        viewready : function() {
            if (this.gridExtras == null || this.gridExtras.doAutoColWidth) {
                this.setColumnWidthsFromDataWhenReady('grid viewready');
            }
        },
        afterrender: function(obj) {
            // Normally the gridpanel filter feature menus do not get created
            // until a user physically clicks on one of them.
            // To handle this you can explicitly create them in the gridpanel's afterrender event
            // so that they are available right away. thanks Geronimo, go falcons.
            obj.filters.createFilters();

            var menu = this.headerCt.getMenu();
            menu.add([
                {
                    icon : 'resources/images/columns.gif',
                    text : 'Column Label',
                    menu : [obj.customLabelMenu]
                },
                {
                    icon : 'resources/images/columns.gif',
                    text : 'Format Data',
                    menu : [obj.formatComboMenu]
                }
            ]);
        },
        columnhide : function(ct, column, obj) {
            // grid.setVisibleColumns();
        },
        columnshow : function(ct, column, obj) {
            // grid.setVisibleColumns();
        }
    },

    setVisibleColumns: function () {
        // if (Ext.getCmp('aggregatebutton-' + this.tabId).pressed) {
        //     // var store   = Ext.getCmp('grid-panel-' + this.tabId).getStore();
        //     var grid = Ext.getCmp('grid-panel-' + this.tabId);
        // }
        var columns = this.columns;
        var visibleColumns = []
        for (var i = 0; i < columns.length; i++) {
            if (!columns[i].hidden) {
                visibleColumns.push(columns[i].dataIndex);
            }
        }
        this.visibleColumns = visibleColumns;
        // store.proxy.extraParams['visible_columns'] = visibleColumns.join(",");
        // store.reload();
    },

    getVisibleColumns: function () {
        this.setVisibleColumns();
        return(this.visibleColumns);
    },

    viewConfig : {
        enableTextSelection: true
    },

    newModelInstance: function () {
        var model = this.getModel();
        var rec = new model();
        //var rec = new Writer.Person({
        //    first: '',
        //    last: '',
        //    email: ''
        //});
        return(rec);
    },

    // getModelName() defines the model if it does not yet exist and returns the name
    getModelName: function (skipAutoDefinition) {
        var modelName = 'FrontSuite.model.'+this.tabId;
        if (!skipAutoDefinition) {
            var model = Ext.ModelManager.getModel(modelName);
            if (!model) {
                this.defineModel();
            }
        }
        return(modelName);
    },

    // getModel() defines the model if it does not yet exist and returns the model class
    getModel: function () {
        var modelName = this.getModelName(true);
        var model = Ext.ModelManager.getModel(modelName);
        if (!model) {
            this.defineModel();
            model = Ext.ModelManager.getModel(modelName);
        }
        return(model);
    },

    defineModel: function() {
        var viewDef = this.viewDef;
        var modelName = this.getModelName(true);
        var modelFields = [];
        var modelValidations = [];
        var column_name, modelField;
        
        for (var i = 0; i < viewDef.columns.length; i++) {
            column_name = viewDef.columns[i];
            modelFields.push(column_name);       // this is temporary until I get the rest working
//            if (viewDef.column[column_name]) {
//                var label         = viewDef.column[column_name].column_label;
//                var columnType    = viewDef.column[column_name].column_type;
//
//                modelField = {
//                    name      : column_name,
//                    dataIndex : column_name,
//                    type      : columnType,
//                };
//            }
//            else {
//                // TODO: Figure out why the server EVER returns columns which are not in the viewDef.column hash.
//                //       This is the safety-net work-around.
//                modelField = {
//                    name      : column_name,
//                    dataIndex : column_name,
//                    useNull   : true
//                };
//            }
//
//            modelFields.push(modelField);
        }

        var modelConfig = {
            extend: 'Ext.data.Model',
            fields: modelFields
        };
        if (modelValidations.length > 0) modelConfig.validations = modelValidations;

        Ext.define(modelName, modelConfig);
    },

    // var gridStore     = this.createTableDataStore (this.tabId, this.databaseName, this.tableName,
    //                                                this.viewDef.columns, this.viewDef.sorters, this.isAggregate, this.itemsPerPage);
    createTableDataStore : function () {
        var columnNames = this.viewDef.columns;
        var model = this.getModel();
        var extraParams = {columns : columnNames.join(","), rowtype : 'object', aggregate : this.isAggregate};
        if (this.isAggregate) {
            var visible_columns = this.getVisibleColumns();
            extraParams.visible_columns = visible_columns;
        }
        var tableDataStore = Ext.create('Ext.data.JsonStore', {
            autoLoad   : true,
            storeId    : 'table-data-store-' + this.tabId,
            pageSize   : this.gridItemsPerPage,
            remoteSort : true,
            proxy      : Ext.create('Ext.data.HttpProxy',{
                timeout: 90000, // 90 seconds
                type        : 'ajax',
                extraParams : {columns : columnNames.join(","), rowtype : 'object', aggregate : this.isAggregate},
                url         : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+this.databaseName+'/'+this.tableName,
                reader : {
                    type           : 'json',
                    root           : 'data',
                    totalProperty  : 'total'
                },
                writer: {
                    type           : 'json',
                    root           : 'data',
                    writeAllFields : false
                },
                listeners: {
                    exception: function(proxy, response, operation){
console.log('createTableDataStore()');
console.log(proxy);
console.log(response);
console.log(operation);
//debugger;
                        var message = response.message || operation.getError();
                        Ext.MessageBox.show({
                            title:  'Remote Error',
                            msg:     message,
                            icon:    Ext.MessageBox.ERROR,
                            buttons: Ext.Msg.OK
                        });
                    }
                }
            }),
            'model'      : model,
            'sortOnLoad' : true,
            'sorters'    : this.gridExtras.sorters
        });

        return(tableDataStore);
    },

    createTableDataStoreSql : function (viewDef, tabId, dbName, data) {
        var tableDataStore = Ext.create('Ext.data.JsonStore',{
            autoLoad   : true,
            storeId    : 'table-data-store-' + tabId,
            pageSize   : 100,
            remoteSort : true,
            proxy      : Ext.create('Ext.data.HttpProxy',{
                timeout: 90000, // 90 seconds
                type        : 'ajax',
                url         : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+dbName+'/x',
                extraParams : {sql : viewDef.sql},
                method      : 'GET',
                reader : {
                    type          : 'json',
                    root          : 'data',
                    totalProperty : 'total'
                }
            }),
            fields     : viewDef.columns
            //sortOnLoad : true,
            //sorters    : data.sorters
        });

        return(tableDataStore);
    },

    setColumnWidthsFromDataWhenReady: function (auto_width_reason) {
        //console.log('setColumnWidthsFromDataWhenReady('+auto_width_reason+')');
        var maxDataLength = {};

        var store        = Ext.getCmp('grid-panel-' + this.tabId).getStore();
        var theStore     = [store];
        var len          = theStore.length;
        var loadedStores = 0;
        var grid         = this;

        function check() {
            if (++loadedStores == len) {
                grid.setColumnWidthsFromData();
            }
        }

        if (store.isLoading() == false) {
            grid.setColumnWidthsFromData();
        }

        for (var i = 0; i < len; ++i) {
            theStore[i].on('load', check, null, {single: true});
        }
    },

    setColumnWidthsFromData: function () {
        //console.log('setColumnWidthsFromData()');
        var maxDataLength = {};

        var columns = Ext.getCmp('grid-panel-' + this.tabId).columns;
        var records = Ext.getCmp('grid-panel-' + this.tabId).getStore().data.items;

        for (var i = 0; i < records.length; i++) {
            var record = records[i].data;

            for (var k = 0; k < columns.length; k++) {
                var colName = columns[k].dataIndex;

                var dataLength = 0;

                if (record[colName] != null) {
                    dataLength = record[colName].length;
                }

                if (maxDataLength[colName] == null) {
                    maxDataLength[colName] = dataLength;
                }
                else if (dataLength > maxDataLength[colName]) {
                    maxDataLength[colName] = dataLength;
                }
            }
        }

        if (records.length > 0) {
            for (var c = 0; c < columns.length; c++) {
                columns[c].width = Math.min(200,Math.round(Math.max(columns[c].dataIndex.length, maxDataLength[columns[c].dataIndex]) * 7.5)+4);
            }
            Ext.getCmp('grid-panel-' + this.tabId).getView().refresh();
        }
        else {
            for (var c = 0; c < columns.length; c++) {
                columns[c].width = Math.min(200,Math.round(columns[c].dataIndex.length * 7.5)+4);
            }
            Ext.getCmp('grid-panel-' + this.tabId).getView().refresh();
        }
    },

    createSaveButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var gridSaveButton = Ext.create('Ext.Action', {
            id      : 'gridtoolbar-save-' +  grid.tabId,
            tooltip : {text : 'Save the state of the grid, i.e. column width'},
            icon    : 'resources/images/save.gif',
            text    : 'Save View',
            handler : function(widget, event) {
                var colObj     = [];
                var saveData = {};
                var columns    = Ext.getCmp('grid-panel-' + grid.tabId).columns;
                var filters    = Ext.getCmp('grid-panel-' + grid.tabId).filters.getFilterData();

                var filterObj = {};
                for (var i = 0; i < filters.length; i++) {
                    filterObj[filters[i].field] = {type : filters[i].data.type, value : filters[i].data.value};
                }

                for (var i = 0; i < columns.length; i++) {
                    var sortState = columns[i].sortState;
                    var stateId   = columns[i].stateId;
                    var colName   = columns[i].dataIndex;
                    var colText   = columns[i].text;
                    var hidden    = columns[i].hidden;
                    var width     = columns[i].width;
                    var colDef    = {"id" : stateId, "width" : width, "name" : colName, "column_label" : colText};

                    if (sortState) {
                        colDef.sort = sortState;
                    }
                    if (hidden) {
                        colDef.hidden = hidden;
                    }
                    if (filterObj[colName] != null) {
                        colDef.filter     = filterObj[colName].value;
                        colDef.filtertype = filterObj[colName].type;
                    }

                    colObj.push(colDef);
                }

                saveData.columns = colObj;
                var aggButton = Ext.getCmp('aggregatebutton-' + grid.tabId);
                if (aggButton && aggButton.pressed) {
                    saveData.aggregate = true;
                }
                // saveData = Ext.encode(saveData);

                FrontSuite.util.ext.put({ type: 'grid', key: grid.tabId, data: saveData }, function (response, message) {
                    if (response.success) {
                        FrontSuite.util.ext.message('Status','Changes saved successfully');
                    }
                    else {
                        Ext.MessageBox.show({
                           title   : 'Status',
                           msg     : 'server-side failure: ' + message,
                           buttons : Ext.MessageBox.OK
                        });
                    }
                });
            }
        });

        return(gridSaveButton);
    },

    createTaskButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var gridTaskButton = Ext.create('Ext.Action', {
            id      : 'gridtoolbar-task-' + grid.tabId,
            tooltip : {text : 'Add the view to the Tasks tree'},
            icon    : 'resources/images/add.png',
            text    : 'Add View to Organizer',
            handler : function(widget, event) {
                Ext.Ajax.request({
                    url       : FrontSuite.options.apps_urldir + '/sitefront/front-framework/api.php/service/ext/apptree',
                    params    : {type : 'screens', op : 'new', parent_id : 'src', label : grid.tableLabel, action: grid.tabId},
                    method    : 'GET',
                    autoAbort : false,
                    success : function(response) {
                        var appTreeResponse = Ext.JSON.decode (response.responseText);
                        var treeNodeId = appTreeResponse.tree_id;
                        var root = Ext.getCmp('screen-tree-panel').getRootNode();

                        root.appendChild({
                            id :  treeNodeId,
                            text: grid.tableLabel,
                            action: grid.tabId,
                            leaf: true
                        });
                    },
                    failure: function(response, opts) {
                        Ext.MessageBox.show({
                           title   : 'Status',
                           msg     : 'server-side failure with status code ' + response.status,
                           buttons : Ext.MessageBox.OK
                        });
                    }
                });
            }
        });

        return(gridTaskButton);
    },

    createSqlButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var gridSqlButton = Ext.create('Ext.Action', {
            id      : 'gridtoolbar-sql-' + grid.tabId,
            tooltip : {text : 'Show Sql'},
            icon    : 'resources/images/preview.png',
            text    : 'Show Sql',
            handler : function(widget, event) {

                var store = Ext.getCmp('grid-panel-' + grid.tabId).getStore();
                var sql   = store.proxy.reader.rawData.sql;

                Ext.create('Ext.window.Window', {
                    id           : 'window.sql.' + grid.tabId,
                    width        : 550,
                    maxHeight    : 550,
                    shadowOffset : 25,
                    y            : 50,
                    title        : 'Sql',
                    layout       : 'fit',
                    tools  : [{
                        type    : 'save',
                        handler : function() {
                            if (grid.viewName) {

                                var sql        = Ext.getCmp('textareafield.sql.' + grid.tabId).getValue();
                                var colObj     = [];
                                var saveData = {};
                                var columns    = Ext.getCmp('grid-panel-' + grid.tabId).columns;

                                for (var i = 0; i < columns.length; i++) {
                                    var stateId   = columns[i].stateId;
                                    var colName   = columns[i].dataIndex;
                                    var colText   = columns[i].text;
                                    var hidden    = columns[i].hidden;
                                    var width     = columns[i].width;
                                    var colDef    = {"id" : stateId, "width" : width, "name" : colName, "column_label" : colText};

                                    if (hidden) {
                                        colDef.hidden = hidden;
                                    }

                                    colObj.push(colDef);
                                }

                                saveData.columns = colObj;
                                saveData.sql     = sql;

                                // saveData = Ext.encode(saveData);

                                FrontSuite.util.ext.put({ type: 'grid', key: grid.tabId, data: saveData }, function (response, message) {
                                    if (response.success) {
                                        Ext.Ajax.request({
                                            url       : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+grid.databaseName+'/'+grid.tableName+'/_meta',
                                            params    : {view : grid.tabId},
                                            method    : 'GET',
                                            autoAbort : false,
                                            success : function(response) {
                                                var viewDef        = Ext.JSON.decode (response.responseText);
                                                this.gridExtras    = {};
                                                var columnArray    = FrontSuite.db.getTableDefColsSql (viewDef, this.gridExtras);
                                                var tableDataStore = grid.createTableDataStoreSql(viewDef, grid.tabId, grid.databaseName, this.gridExtras);
                                                var gridView       = Ext.getCmp('grid-panel-' + grid.tabId);

                                                var dockedItems = gridView.getDockedComponent(Ext.getCmp('toolbar-grid-panel-' + grid.tabId)).items;
                                                var itemsToRemove = [];
                                                for (var i = 0; i < dockedItems.length; i++) {
                                                    if (dockedItems[i].id == 'aggregatebutton-' + grid.tabId || dockedItems[i].id == 'relationship.combo.' + grid.tabId) {
                                                        itemsToRemove.push(dockedItems[i].id);
                                                    }
                                                }
                                                for (var i = 0; i < itemsToRemove.length; i++) {
                                                     gridView.getDockedComponent(Ext.getCmp('toolbar-grid-panel-' + grid.tabId)).remove(itemsToRemove[i]);
                                                }

                                                gridView.reconfigure(tableDataStore, columnArray);

                                                grid.customLabelMenu = grid.createCustomLabelMenu();
                                                grid.formatComboMenu = grid.createFormatComboMenu();
                                                var menu = gridView.headerCt.getMenu();
                                                menu.add([
                                                    {
                                                        icon : 'images/columns.gif',
                                                        text : 'Column Label',
                                                        menu : [grid.customLabelMenu]
                                                    },
                                                    {
                                                        icon : 'images/columns.gif',
                                                        text : 'Format Data',
                                                        menu : [grid.formatComboMenu]
                                                    }
                                                ]);

                                                grid.down('#pager').bindStore(tableDataStore, true);

                                                grid.setColumnWidthsFromDataWhenReady('save');

                                                Ext.getCmp('window.sql.' + grid.tabId).close();
                                            }
                                        });
                                    }
                                    else {
                                        Ext.MessageBox.show({
                                           title   : 'Status',
                                           msg     : 'server-side failure: ' + message,
                                           buttons : Ext.MessageBox.OK
                                        });
                                    }
                                });
                            }
                            else {
                                Ext.MessageBox.show({
                                    title   : 'Status',
                                    msg     : 'You can only save Sql in a copied view.',
                                    buttons : Ext.MessageBox.OK
                                });
                            }
                        }
                    }],
                    items: [{
                        xtype : 'textareafield',
                        id    : 'textareafield.sql.' + grid.tabId,
                        grow  : true,
                        value : sql,
                        fieldStyle: {
                            'fontSize' : '11px'
                        }
                    }]
                }).show();
            }
        });

        return(gridSqlButton);
    },

    createFormatComboMenu: function (results) {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var formatComboMenu = new Ext.form.ComboBox({
            id           : 'formatComboMenu.' + grid.tabId,
            width        : 140,
            displayField : 'id',
            valueField   : 'id',
            store : new Ext.data.ArrayStore({
                fields : ['id'],
                data   : []
            }),
            listeners : {
                select : function (obj, records, eOpts ) {
                    var grid = Ext.getCmp('grid-panel-' + grid.tabId);
                    var columnDataIndex = grid.headerCt.getMenu().activeHeader.dataIndex;
                    var gridColumns = grid.headerCt.getGridColumns();

                    var columnArray = [];
                    var colObj;

                    for (var i = 0; i < gridColumns.length; i++) {
                        var headerVal     = gridColumns[i].text;
                        var sortableVal   = gridColumns[i].sortable;
                        var dataIndexVal  = gridColumns[i].dataIndex;
                        var widthVal      = gridColumns[i].width;
                        var filterableVal = gridColumns[i].filterable;
                        var hiddenVal     = gridColumns[i].hidden;
                        var typeVal       = gridColumns[i].type; //integer,string,float,datetime
                        var xtypeVal      = gridColumns[i].xtype;
                        var formatVal     = gridColumns[i].format;
                        var rendererVal   = gridColumns[i].renderer;

                        colObj = {header : headerVal, sortable : sortableVal, dataIndex : dataIndexVal,
                            width : widthVal, filterable : filterableVal, hidden : hiddenVal, type : typeVal,
                            listeners: {headertriggerclick: function(obj, col) {FrontSuite.db.createFormatStore(obj, col, results);}}
                        };

                        if (formatVal) {
                            colObj.xtype  = xtypeVal;
                            colObj.format = formatVal;
                        }
                        else if (rendererVal && typeVal == 'string') {
                            colObj.renderer = rendererVal;
                        }

                        if (gridColumns[i].dataIndex == columnDataIndex) {
                            if (this.getValue() == 'default') {
                                colObj.xtype    = '';
                                colObj.format   = '';
                                colObj.renderer = '';
                            }
                            else {
                                if (typeVal == 'integer' || typeVal == 'float') {
                                    colObj.xtype  = 'numbercolumn';
                                    colObj.format = this.getValue();
                                }
                                else if (typeVal == 'string') {
                                    if (this.getValue() == 'uppercase') {
                                        colObj.renderer = Ext.util.Format.uppercase;
                                    }
                                    else if (this.getValue() == 'lowercase') {
                                        colObj.renderer = Ext.util.Format.lowercase;
                                    }
                                }
                            }
                        }
                        columnArray.push(colObj);
                    }

                    var store = Ext.getCmp('grid-panel-' + grid.tabId).getStore();
                    grid.reconfigure(store, columnArray);

                    grid.customLabelMenu = grid.createCustomLabelMenu();
                    grid.formatComboMenu = grid.createFormatComboMenu(results);
                    var menu = grid.headerCt.getMenu();
                    menu.add([
                        {
                            icon : 'resources/images/columns.gif',
                            text : 'Column Label',
                            menu : [grid.customLabelMenu]
                        },
                        {
                            icon : 'resources/images/columns.gif',
                            text : 'Format Data',
                            menu : [grid.formatComboMenu]
                        }
                    ]);
                }
            }
        });

        return(formatComboMenu);
    },

    createCustomLabelMenu: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var customLabelMenu = Ext.create('Ext.form.field.Text', {
            id : 'customLabelMenu.' + grid.tabId,
            enableKeyEvents : true,
            listeners : {
                keypress: function (obj, e) {
                    if (e.keyCode == e.ENTER) {
                        var grid            = Ext.getCmp('grid-panel-' + grid.tabId);
                        var columnText      = grid.headerCt.getMenu().activeHeader.text;
                        var columnDataIndex = grid.headerCt.getMenu().activeHeader.dataIndex;

                        var gridColumns = grid.headerCt.getGridColumns();

                        for (var i = 0; i < gridColumns.length; i++) {
                            if (gridColumns[i].dataIndex == columnDataIndex) {
                                grid.columns[i].setText(this.getValue());
                            }
                        }
                    }
                }
            }
        });
        return(customLabelMenu);
    },

    createSaveAsButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var gridSaveAsButton = Ext.create('Ext.Action', {
            id      : 'gridtoolbar-saveas-' + grid.tabId,
            tooltip : {text : 'Copy the view to the Tables tree'},
            icon    : 'resources/images/disk.png',
            text    : 'Save As New View',
            handler : function(widget, event) {
                Ext.MessageBox.prompt('Copy As', 'Please enter name:', function(btn, folderVal) {
                    if (btn == 'ok') {
                        var textVal    = folderVal;
                        var colObj     = [];
                        var saveData = {};
                        var columns    = Ext.getCmp('grid-panel-' + grid.tabId).columns;
                        var filters    = Ext.getCmp('grid-panel-' + grid.tabId).filters.getFilterData();

                        var filterObj = {};
                        for (var i = 0; i < filters.length; i++) {
                            filterObj[filters[i].field] = {type : filters[i].data.type, value : filters[i].data.value};
                        }

                        for (var i = 0; i < columns.length; i++) {
                            var sortState = columns[i].sortState;
                            var stateId   = columns[i].stateId;
                            var colName   = columns[i].dataIndex;
                            var colText   = columns[i].text;
                            var hidden    = columns[i].hidden;
                            var width     = columns[i].width;
                            var colDef    = {"id" : stateId, "width" : width, "name" : colName, "column_label" : colText};

                            if (sortState) {
                                colDef.sort = sortState;
                            }
                            if (hidden) {
                                colDef.hidden = hidden;
                            }
                            if (filterObj[colName] != null) {
                                colDef.filter     = filterObj[colName].value;
                                colDef.filtertype = filterObj[colName].type;
                            }

                            colObj.push(colDef);
                        }

                        saveData.columns = colObj;

                        if (Ext.getCmp('aggregatebutton-' + grid.tabId).pressed) {
                             saveData.aggregate = true;
                        }
                        // saveData = Ext.encode(saveData);

                        FrontSuite.util.ext.put({ type: 'grid', key: grid.tabId, label: textVal, data: saveData }, function (response, message) {
                            if (response.success) {
                                var selectedNode = Ext.getCmp('table-tree-panel').getSelectionModel().getSelection()[0];

                                if (selectedNode.data.leaf) {
                                    var modText      = textVal.replace(/\s+/g,"_");
                                    var modTextLower = modText.toLowerCase();

                                    var nodeId = grid.databaseName +'.'+ grid.tableName +'.'+ modTextLower;

                                    selectedNode.parentNode.insertBefore({
                                        id     : nodeId,
                                        text   : textVal,
                                        leaf   : true
                                    }, selectedNode);
                                }
                                else {
                                    var store        = Ext.getCmp('table-tree-panel').getStore();
                                    var modText      = textVal.replace(/\s+/g,"_");
                                    var modTextLower = modText.toLowerCase();
                                    var nodeId       = grid.tabId + '.' +  modTextLower;

                                    selectedNode.expand();

                                    if (selectedNode.store.treeStore.tree.nodeHash[nodeId] == null && store.isLoading() == false) {
                                        selectedNode.appendChild({
                                            id     : nodeId,
                                            text   : textVal,
                                            action : null,
                                            leaf   : true
                                        });
                                    }
                                }
                                FrontSuite.util.ext.message('Status','Changes saved successfully');
                            }
                            else {
                                Ext.MessageBox.show({
                                   title   : 'Status',
                                   msg     : 'server-side failure: ' + message,
                                   buttons : Ext.MessageBox.OK
                                });
                            }
                        });
                    }
                });
            }
        });

        return(gridSaveAsButton);
    },

    createOperationsMenu: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var gridMenu = Ext.create('Ext.menu.Menu', {
            id    : 'gridtoolbar-menu-' + grid.tabId,
            style : {
                overflow : 'visible'
            },
            items: [
                {
                    text    : 'Auto Width',
                    tooltip : {text : 'Adjust all column widths in grid based on data length'},
                    handler : function(widget, event) {
                        grid.setColumnWidthsFromDataWhenReady('auto-width menu');
                    }
                },
                {
                    text    : 'Remove Hidden Columns',
                    tooltip : {text : 'Remove hidden columns from grid view. Saved views only.'},
                    handler : function(widget, event) {
                        if (grid.viewName) {
                            var colObj     = [];
                            var saveData = {};
                            var columns    = Ext.getCmp('grid-panel-' + grid.tabId).columns;
                            var filters    = Ext.getCmp('grid-panel-' + grid.tabId).filters.getFilterData();

                            var filterObj = {};
                            for (var i = 0; i < filters.length; i++) {
                                filterObj[filters[i].field] = {type : filters[i].data.type, value : filters[i].data.value};
                            }

                            for (var i = 0; i < columns.length; i++) {

                                var hidden = columns[i].hidden;

                                if (hidden == true) {
                                   continue;
                                }

                                var sortState = columns[i].sortState;
                                var stateId   = columns[i].stateId;
                                var colName   = columns[i].dataIndex;

                                var width     = columns[i].width;
                                var colDef    = {"id" : stateId, "width" : width, "name" : colName};

                                if (sortState) {
                                    colDef.sort = sortState;
                                }
                                if (hidden) {
                                    colDef.hidden = hidden;
                                }
                                if (filterObj[colName] != null) {
                                    colDef.filter     = filterObj[colName].value;
                                    colDef.filtertype = filterObj[colName].type;
                                }

                                colObj.push(colDef);
                            }

                            saveData.columns = colObj;
                            if (Ext.getCmp('aggregatebutton-' + grid.tabId).pressed) {
                                saveData.aggregate = true;
                            }
                            // saveData = Ext.encode(saveData);

                            FrontSuite.util.ext.put({ type: 'grid', key: grid.tabId, data: saveData }, function (response, message) {
                                if (response.success) {
                                    Ext.Ajax.request({
                                        url       : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+grid.databaseName+'/'+grid.tableName+'/_meta',
                                        params    : {view : grid.tabId},
                                        method    : 'GET',
                                        autoAbort : false,
                                        success : function(response) {
                                            var viewDef        = Ext.JSON.decode (response.responseText);

                                            var isAggregate = false;
                                            if (viewDef.aggregate) {
                                                isAggregate = true;
                                            }

                                            var data        = {};
                                            var columnArray    = this.columns;
                                            var tableDataStore = this.store;
                                            var gridView       = Ext.getCmp('grid-panel-' + grid.tabId);

                                            gridView.reconfigure(tableDataStore, columnArray);

                                            //There was no "filter" parameter in a request anymore after reconfigure.
                                            //This is the fix for a bug.
                                            gridView.filters.bindStore(tableDataStore);

                                            grid.down('#pager').bindStore(tableDataStore, true);

                                            grid.setColumnWidthsFromDataWhenReady('remove-hidden-columns menu');
                                        }
                                    });
                                }
                                else {
                                    Ext.MessageBox.show({
                                       title   : 'Status',
                                       msg     : 'server-side failure: ' + message,
                                       buttons : Ext.MessageBox.OK
                                    });
                                }
                            });
                        }
                        else {
                             Ext.MessageBox.show({
                                 title   : 'Status',
                                 msg     : 'You can only remove columns from a saved view.',
                                 buttons : Ext.MessageBox.OK
                             });
                        }
                    }
                }
            ]
        });

        return(gridMenu);
    },

    createAggregateButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var aggregateButton = Ext.create('Ext.button.Button', {
            // NOTE: I want this Aggregate toggle-able pushbutton to replace the aggregate checkbox
            id: 'aggregatebutton-' + this.tabId,
            text: 'Aggregate',
            iconCls: 'icon-sum',
            disabled: !this.isAggregateOK,
            enableToggle: true,
            pressed: this.isAggregate,
            tooltip: 'When enabled, queries are aggregate queries, producing counts and sums of columns as appropriate.',
            scope: this,
            toggleHandler: function(btn, pressed) {
                grid.isAggregate = pressed;
                if (pressed) {
                    Ext.Ajax.request({
                        url       : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+grid.databaseName+'/'+grid.tableName+'/_meta',
                        params    : {view : grid.tabId, aggregate : true},
                        method    : 'GET',
                        autoAbort : false,
                        success : function(response, opts) {
                            var viewDef = Ext.JSON.decode (response.responseText);

                            var data        = {};
                            var columnArray    = FrontSuite.db.getTableDefCols (viewDef, data);
                            var tableDataStore = grid.createTableDataStore (viewDef, grid.tabId, grid.databaseName, data);
                            var gridView       = Ext.getCmp('grid-panel-' + grid.tabId);

                            tableDataStore.proxy.extraParams['aggregate'] = true;

                            gridView.reconfigure(tableDataStore, columnArray);

                            //There was no "filter" parameter in a request anymore after reconfigure.
                            //This is the fix for a bug.
                            gridView.filters.bindStore(tableDataStore);

                            grid.down('#pager').bindStore(tableDataStore, true);

                            grid.setColumnWidthsFromDataWhenReady('aggregate pressed');

                            var columns     = gridView.columns;
                            var visibleColumns = []
                            for (var i = 0; i < columns.length; i++) {
                                var hidden = columns[i].hidden;
                                if (!hidden) {
                                    visibleColumns.push(columns[i].dataIndex);
                                }
                            }

                            tableDataStore.proxy.extraParams['visible_columns'] = visibleColumns.join(",");
                        }
                    })
                }
                else {
                    Ext.Ajax.request({
                        url       : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+grid.databaseName+'/'+grid.tableName+'/_meta',
                        params    : {view : grid.tabId, aggregate : false},
                        method    : 'GET',
                        autoAbort : false,
                        success : function(response, opts) {
                            var viewDef = Ext.JSON.decode (response.responseText);

                            var data           = {};
                            var columnArray    = FrontSuite.db.getTableDefCols (viewDef, data);
                            var tableDataStore = grid.createTableDataStore (viewDef, grid.tabId, grid.databaseName, data);
                            var gridView       = Ext.getCmp('grid-panel-' + grid.tabId);

                            tableDataStore.proxy.extraParams['aggregate'] = false;

                            gridView.reconfigure(tableDataStore, columnArray);

                            //There was no "filter" parameter in a request anymore after reconfigure.
                            //This is the fix for a bug.
                            gridView.filters.bindStore(tableDataStore);

                            grid.down('#pager').bindStore(tableDataStore, true);

                            grid.setColumnWidthsFromDataWhenReady('aggregate unpressed');

                            var columns     = gridView.columns;
                            var visibleColumns = []
                            for (var i = 0; i < columns.length; i++) {
                                var hidden = columns[i].hidden;
                                if (!hidden) {
                                    visibleColumns.push(columns[i].dataIndex);
                                }
                            }

                            tableDataStore.proxy.extraParams['visible_columns'] = visibleColumns.join(",");
                        }
                    })
                }
            }
        });
        return(aggregateButton);
    },

    createOperationsMenuButton: function (gridMenu) {
        var gridMenuButton = Ext.create('Ext.Action', {
            text : 'Misc Operations',
            icon : 'resources/images/grid.png',
            menu : gridMenu
        });

        return(gridMenuButton);
    },

    createRelationshipsComboBox: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var rel  = grid.relationship;
        var rels = grid.relationships;
        var gridComboStoreData = [];
        if (rel != null) {
            for (var i = 0; i < rels.length; i++) {
                var array = [];
                var label = rel[rels[i]].relationship_label;
                var value = rel[rels[i]].relationship_alias;
                array.push(value);
                array.push(label);
                gridComboStoreData.push(array);
            }
        }

        var gridComboBox = {};

        if (gridComboStoreData.length > 0) {

            var gridComboStore = Ext.create('Ext.data.ArrayStore', {
                fields : ['value', 'label'],
                data   : gridComboStoreData
            });

            gridComboBox = Ext.create('Ext.form.field.ComboBox', {
                id           : 'relationship.combo.' + grid.tabId,
                fieldLabel   : 'Add Columns',
                multiSelect  : true,
                labelAlign   : 'right',
                labelPad     : 3,
                displayField : 'label',
                valueField   : 'value',
                width        : 275,
                store        : gridComboStore,
                listeners : {
                    'change': function () {
                        Ext.Ajax.request({
                            url       : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+grid.databaseName+'/'+grid.tableName+'/_meta',
                            params    : {relationships : gridComboBox.getValue().join(","), view : grid.tabId},
                            method    : 'GET',
                            autoAbort : false,
                            success : function(response) {
                                var viewDef = Ext.JSON.decode (response.responseText);

                                var isAggregate = false;
                                if (viewDef.aggregate) {
                                    isAggregate = true;
                                }

                                var data           = {};
                                var columnArray    = FrontSuite.db.getTableDefCols (viewDef, data);
                                var tableDataStore = grid.createTableDataStore (viewDef, grid.tabId, grid.databaseName, data, isAggregate);
                                var gridView       = Ext.getCmp('grid-panel-' + grid.tabId);

                                gridView.reconfigure(tableDataStore, columnArray);

                                //There was no "filter" parameter in a request anymore after reconfigure.
                                //This is the fix for a bug.
                                gridView.filters.bindStore(tableDataStore);

                                grid.down('#pager').bindStore(tableDataStore, true);

                                this.setColumnWidthsFromDataWhenReady('relationship combo changed');
                            }
                        });
                    }
                }
            });
        }
        return(gridComboBox);
    },

    createExportMenu: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var exportMenu = Ext.create('Ext.menu.Menu', {
            id     : 'export.menu.' + grid.tabId,
            style  : {
                overflow : 'visible'
            },
            items  : [
                {
                    text    : 'To File',
                    tooltip : {text : 'Export this tab\'s data to a file'},
                    handler : function(widget, event) {
                        msg = "File Name: <input type=\"text\" id=\"fname\">" +
                              "<select id=\"format\"><option value='CSV'>.csv</option><option value='XLS'>.xls (todo)</option></select>" +
                              "<br><br>" +
                              "Save To : <select id=\"to\"><option value='.'>/ (Root Directory)</option>";

                        var nodes = documentTreeStore.tree.nodeHash;

                        for (var key in nodes) {
                            // skip non dirs and root dir
                            if ( (nodes[key].raw.leaf != undefined && nodes[key].raw.leaf == true) || key == "src")
                                continue;
                            else if ( /'/.test(key) )
                                key = key.replace(/'/,"\\\'");

                            msg += "<option value='" + key + "'>" + key + "</option>";
                        }

                        msg += "</select><br>" +
                               "Data Range: <select id=\"scope\"><option value='CURRENT_PAGE'>Current Page Only</option><option value='ALL_PAGES'>All Pages</option></select>" +
                               "<br><br>" +
                               "<input type=\"checkbox\" id=\"overwrite\"> Overwrite existing files with same name?";

                        Ext.MessageBox.show({
                                title   : "Export Data",
                                width   : 300,
                                msg     : msg,
                                buttons : Ext.MessageBox.OKCANCEL,
                                fn: function(btn) {
                                    if ( btn == 'ok') {

                                        var gridViewCols = Ext.getCmp('grid.panel.' + grid.tabId).columns;
                                        var isAggregate   = Ext.getCmp('aggregatebutton-' + grid.tabId).checked;
                                        var columns      = new Array();
                                        var fname        = Ext.get('fname').getValue();
                                        var format       = Ext.get('format').getValue();
                                        var scope        = Ext.get('scope').getValue();
                                        var to           = Ext.get('to').getValue();
                                        var overwrite    = (document.getElementById('overwrite').checked == true) ? 1 : 0;

                                        gridViewCols.forEach( function(element, index, array) { columns.push(element.dataIndex) });

                                        Ext.Ajax.request({
                                            url       : FrontSuite.options.apps_urldir+'/sitefront/front-framework/api.php/object/'+grid.databaseName+'/'+grid.tableName,
                                            params    : { columns : columns.join(","), rowtype : 'object', aggregate : isAggregate,
                                                          format : format, fname : fname, overwrite : overwrite, to : to},
                                            method    : 'GET',
                                            autoAbort : false,
                                            success : function(response) {
                                                if (Ext.decode(response.responseText).success == true) {
                                                    storeExpandedDocumentTreeNodes();
                                                    documentTreeStore.reload();
                                                }
                                                else {
                                                    alert(Ext.decode(response.responseText).msg);
                                                }
                                            },
                                            failure: function(response, opts) {
                                                Ext.MessageBox.show({
                                                   title   : 'Status',
                                                   msg     : 'server-side failure with status code ' + response.status,
                                                   buttons : Ext.MessageBox.OK
                                                });
                                            }
                                        });
                                    }
                                }
                            });
                    }
                },
                {
                    text    : 'Selected Rows To Clipboard',
                    tooltip : {text : 'Copies highlighted rows to clipboard as a comma-delimited string'},
                    handler : function(widget, event) {

                        var selectedNodes = Ext.getCmp('grid.panel.' + grid.tabId).getSelectionModel().getSelection();

                        if (selectedNodes == null || selectedNodes.length <= 0) {
                            Ext.MessageBox.show({
                                msg   : "No rows selected. Please select the rows you wish to export to your clipboard",
                                icon  : Ext.MessageBox.WARNING,
                                title : "Error!",
                                width : 175,
                            });
                        }
                        else {

                            var msg = "<input type=\"checkbox\" id=\"include_headers\" checked> Include Column Headers?" +
                                  "<br><br>" +
                                  "Select Delimiter Type:<br>" +
                                  "<select id=\"type\"><option value=\"CSV\">CSV-Style (quote fields containing delimiter)</option><option value=\"PIPE\">Pipe-Style (escape delimiters in data)</option></select>" +
                                  "<br><br>" +
                                  "Delimiter : <input id=\"delimiter\" name=\"delimiter\" type=\"text\" value=\",\" size=\"5\" \>" +
                                  "<br><br>" +
                                  "<input type=\"checkbox\" id=\"useNullKeyword\"> Use null Keyword?";

                            Ext.MessageBox.wait({
                                title   : "Export Data to Clipboard",
                                width   : 300,
                                msg     : msg,
                                buttons : Ext.MessageBox.OKCANCEL,
                                fn: function(btn) {
                                    if (btn == 'ok') {
                                        var delimitedString = "";
                                        var columns         = new Array();
                                        var delimiter       = Ext.get('delimiter').getValue();
                                        var includeHeaders  = (document.getElementById("include_headers").checked == true) ? 1 : 0;
                                        //var styleIndex      = document.getElementById('type').selectedIndex;
                                        //var style           = document.getElementById('type').item(styleIndex).value;
                                        var style           = Ext.get('type').getValue();
                                        var useNullKeyword  = (document.getElementById('useNullKeyword').checked == true) ? 1 : 0;
                                        var re              = new RegExp("(" + delimiter + ")", "g");

                                        ///////////////
                                        // headers
                                        ///////////////
                                        for (var col in selectedNodes[0].data) {
                                            columns.push(col);

                                            if (!includeHeaders)
                                                continue;

                                            if ( col == undefined && !useNullKeyword )
                                                delimitedString += delimiter;
                                            else if (re.test(col)) {
                                                if (style == 'CSV')
                                                    delimitedString += '"' + col + '"' + delimiter;
                                                else
                                                    delimitedString += value.replace( re , "\\$1") + delimiter;
                                            }
                                            else
                                                delimitedString += col + delimiter;
                                        }
                                        delimitedString = delimitedString.substr(0, delimitedString.length - delimiter.length);
                                        delimitedString += "\n";

                                        ////////////////
                                        // values
                                        ////////////////

                                        selectedNodes.forEach(function(node) {
                                            for (var colIdx in columns) {
                                                value = node.data[ columns[colIdx] ];

                                                if ( value == undefined && !useNullKeyword )
                                                    delimitedString += delimiter;
                                                else if ( re.test(value) ) {
                                                    if (style == 'CSV')
                                                        delimitedString += '"' + value + '"' + delimiter;
                                                    else
                                                        delimitedString += value.replace( re , "\\$1") + delimiter;

                                                }
                                                    else
                                                    delimitedString += value + delimiter;
                                            }

                                            delimitedString = delimitedString.substr(0, delimitedString.length - delimiter.length);
                                            delimitedString += "\n";
                                        });

                                        var textDisplayWindow = Ext.create('Ext.window.Window', {
                                            title    : 'Please copy this text to your clipboard:',
                                            height   : 200,
                                            width    : 350,
                                            closable : false,
                                            layout   : 'fit',
                                            buttons  : [
                                                {
                                                    text    : 'Done' ,
                                                    handler : function() { Ext.WindowManager.getActive().close(); },
                                                },
                                            ],
                                        });

                                        var textArea = Ext.create('Ext.Component', {
                                            html   : "<textarea readonly=\"readonly\" style=\"resize:none; width:100%; height:100%;\" id=\"csvTextArea\" border=\"0\" >" + delimitedString + "</textarea>",
                                            width  : 300,
                                            height : 200,
                                        });

                                        textDisplayWindow.items.add(textArea);
                                        textDisplayWindow.show();
                                        document.getElementById('csvTextArea').focus();
                                        document.getElementById('csvTextArea').select();
                                    }
                                    else {
                                        return;
                                    }
                                }
                            });
                        }
                    }
                }
            ]
        });

        return (exportMenu);
    },

    createExportMenuButton: function (exportMenu) {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var exportMenuButton = Ext.create('Ext.Action', {
            text : 'Export Data',
            menu : exportMenu
        });

        return(exportMenuButton);
    },

    createSelTypeButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var selTypeButton = Ext.create('Ext.button.Split', {
            id      : grid.tabId+'-seltype',
            tooltip : {text : 'Toggle whether a mouse-click selects a row or a cell'},
            // icon    : 'resources/icons/table_row.png',
            text    : 'Row Mode',
            iconCls : 'icon-table-row',
            menu   : [
                { text: 'Row Mode',  iconCls: 'icon-table-row',  handler:
                  function (btn) {
                      var sb = btn.up('splitbutton'); sb.setText('Row Mode'); sb.setIconCls('icon-table-row');
                      // console.log(btn); FrontSuite.app.message('Button','Row Mode');
                  }
                },
                { text: 'Cell Mode', iconCls: 'icon-table-cell', handler:
                  function (btn) {
                      var sb = btn.up('splitbutton'); sb.setText('Cell Mode'); sb.setIconCls('icon-table-cell');
                      // console.log(btn); FrontSuite.app.message('Button','Cell Mode');
                  }
                }
            ],
            handler : function(widget, event) {
                if (widget.iconCls == 'icon-table-cell') {
                    widget.setText('Row Mode'); widget.setIconCls('icon-table-row');
                }
                else {
                    widget.setText('Cell Mode'); widget.setIconCls('icon-table-cell');
                }
                // console.log(widget);
                // console.log(event);
                // FrontSuite.app.message('Button','Toggling Selection Mode');
            }
        });

        return(selTypeButton);
    },

    createEditButton: function () {
        var grid = this; // We set "this" to a variable "grid" at the beginning so that all kinds of nested handlers can refer to the grid even when the "this" variable changes
        var editButton = Ext.create('Ext.button.Split', {
            id      : grid.tabId+'-edit',
            tooltip : {text : 'Toggle in and out of edit mode'},
            text    : 'Edit',
            iconCls : 'icon-table-edit',
            menu   : [
                { text: 'No Editing (Read-Only) Mode', handler: function () { FrontSuite.app.notify('No Editing (Read-Only) Mode'); } },
                { text: 'Edit in Cell/Spreadsheet Mode', handler: function () { FrontSuite.app.notify('Edit in Cell/Spreadsheet Mode'); } },
                { text: 'Edit in Cell/Spreadsheet Mode', handler: function () { FrontSuite.app.notify('Edit in Cell/Spreadsheet Mode'); } }
            ],
            handler : function(widget, event) {
                FrontSuite.app.notify({message: 'Edit button pressed'});
            }
        });

        return(editButton);
    }

});

